前面一篇已經介紹了Android的SQL使用方式,且配置好了dependency,希望明確的步驟可以使這塊好懂一些
Room的SQL表有很多部分,我會依照上一篇講的順序使用
先改造TodoItem
@Entity("TodoTable")
data class TodoItem(
@PrimaryKey(true)
var id: Int = 0,
var title: String = "",
var content: String = "",
@ColumnInfo("group_name")
var group: String = "",
var done: Boolean = false
)
// for group viewing
data class GroupInfo(
val group: String,
val finished: Int,
val total: Int
)
@Entity
:讓Room知道他是SQL的資料,並給這個表一個名字@PrimaryKey(true)
:定義primary key,並讓資料庫自動生成id的值@ColumnInfo
:因為group與SQL語法撞名,所以換一個在sql中的column name@Dao
interface TodoDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(todoItem: TodoItem)
@Delete
fun delete(todoItem: TodoItem)
@Update
fun update(todoItem: TodoItem)
@Query("""
SELECT group_name AS `group`,
SUM(done) AS finished,
COUNT(*) AS total
FROM TodoTable
GROUP BY group_name""")
fun getGroupsInfo(): Flow<List<GroupInfo>>
@Query("Select * From TodoTable Where group_name = :group Order by title ASC")
fun getGroupItems(group: String): Flow<List<TodoItem>>
@Query("Select * From TodoTable Where group_name = :group Order by done ASC, title ASC")
fun getGroupItemsSorted(group: String): Flow<List<TodoItem>>
}
@Insert(OnConflictStrategy.IGNORE)
:在primary key衝突的時候採取的方式@Query
可以選擇單一參數傳遞,但是查詢語句是靜態的,如果想要動態語句,可以使用RawQuery;Query也可以做delete update等工作@Database(entities = [TodoItem::class], version = 1, exportSchema = false)
abstract class AppDatabase: RoomDatabase() {
abstract fun todoDao(): TodoDao
}
這步就是將前面所有的部件匯入到RoomDatabase 中,它裡面已經寫好一些功能,只需要傳遞操作方式與entity屬性就好
這邊我示範一下converter的用法,因為Color是ULong,並不是可以儲存的datatype,所以要變換型別
@Database(entities = [Task::class], version = 1, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun taskDao(): TaskDao
}
// since color has unstorable type ULong, using ARGB to transform
class Converters {
@TypeConverter
fun fromColor(color: Color): Int = color.toArgb() // 存成 ARGB Int
@TypeConverter
fun toColor(value: Int): Color = Color(value) // 從 ARGB Int 還原
}
因為最後一步就是將viewmodel與database連接,直接使用viewmodel而不需要知道資料來源是MVVM架構的核心
class GlobalViewModelFactory(private val todoDao: TodoDao) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(GlobalVM::class.java)) {
@Suppress("UNCHECKED_CAST")
return GlobalVM(todoDao) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
// At GlobalVM
class GlobalVM(private val todoDao: TodoDao) : ViewModel()
這邊看起來很恐怖,但其實它就是自定義ViewModel的產生方式,因為原本的Factory只能建構無參數的ViewModel,但我們現在的ViewModel有一個參數,所以要自定義生成方式
在大型專案裡,將資料來源(網路API、資料庫)獨立成Repository 幾乎是必備的,它能幫助專案保持結構清晰
但在小型專案中,如果資料來源單純,直接讓 ViewModel 呼叫 DAO 也沒問題